﻿using LaunchAllaround.DesignPattern;
using LaunchAllaround.DesignPattern.MVC;
using LaunchAllaround.Key;
using LaunchAllaround.Pad;
using LaunchAllaround.Playlist;
using LaunchAllaround.Sound;
using Microsoft.Kinect;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;


namespace LaunchAllaround.Engine
{
    /// <summary>
    /// Logique d'interaction pour HandPositionAnalyzer.xaml
    /// </summary>
    public partial class HandPositionAnalyzer : UserControl, View<Kinect>, Controller<PlaylistModel>
    {
        private Skeleton skeleton;
        private double scale;
        private KinectSensor ksensor;
        private int keyDt = 150;//milliseconds
        private int playingDt = 60000 / SongPlayer.myself.Bpm;//milliseconds
        private int padDt = 150;//milliseconds
        private int switchPadDt = 1000;
        private bool startPlayingRight = true;
        private bool startPlayingLeft = true;
        private DateTime timePlayingLeft;
        private DateTime timePlayingRight;
        private DateTime timeLeft;
        private DateTime timeRight;
        private int leftIndex = -1;
        private int rightIndex = -1;

        public HandPositionAnalyzer()
        {
            InitializeComponent();
            this.MainPad.Visibility = Visibility.Visible;
            this.HandRight.Visibility = Visibility.Visible;
            this.HandLeft.Visibility = Visibility.Visible;
        }


        private Model<PlaylistModel> model;
        public Model<PlaylistModel> GetModel()
        {
            return this.model;
        }

        public void SetModel(Model<PlaylistModel> m)
        {
            this.model = m;

            PlaylistModel plModel = (PlaylistModel)this.model;
            PadModel pm = (PadModel)plModel.Get(0);
        }

        public void Refresh(RefreshEvent<Kinect> ev)
        {
            if (ev is RefreshKinectStatus)
            {
                if( ((RefreshKinectStatus)(ev)).Status == KinectStatus.Connected){
                    this.KinectInterractions.Visibility = Visibility.Visible;
                }
                else
                {
                    this.KinectInterractions.Visibility = Visibility.Hidden;
                }
            }
            if (ev is RefreshFrames)
            {
                this.ksensor = ((Kinect)ev.Src).Sensor;
                AllFramesReadyEventArgs args = ((RefreshFrames)ev).Args;
                Skeleton[] skeletonData = new Skeleton[Kinect.Myself.Sensor.SkeletonStream.FrameSkeletonArrayLength];
                

                using (SkeletonFrame skeletonFrame = args.OpenSkeletonFrame()) // Open the Skeleton frame
                {
                    if (skeletonFrame != null && skeletonData != null) // check that a frame is available
                    {
                        skeletonFrame.CopySkeletonDataTo(skeletonData); // get the skeletal information in this frame
                        this.skeleton = skeletonData.Where(s => s.TrackingState == SkeletonTrackingState.Tracked).FirstOrDefault();
                        this.draw();
                        this.detectUserAction();
                    }
                }
            }
        }


        private void draw()
        {
            if (this.skeleton != null && this.model != null)
            {
                this.RefreshScale(this.skeleton.Joints[JointType.ShoulderRight], this.skeleton.Joints[JointType.ShoulderLeft]);
                SetEllipsePosition(HandLeft, skeleton.Joints[JointType.HandLeft]);
                SetEllipsePosition(HandRight, skeleton.Joints[JointType.HandRight]);
                SetPadPosition(MainPad, skeleton.Joints[JointType.Head]);

                PlaylistModel plModel = (PlaylistModel)this.model;
                PadModel pm = (PadModel)plModel.Get(0);
                this.MainPad.Stroke = new SolidColorBrush(pm.Color);
                this.MainPad.Fill = new SolidColorBrush(pm.Color);
            
                if (plModel.Count > 1)
                {
                    this.changePadRight.Visibility = Visibility.Visible;
                    SetSwitchPadRightPosition();
                    this.changePadLeft.Visibility = Visibility.Visible;
                    SetSwitchPadLeftPosition();
        }
                else
        {
                    this.changePadRight.Visibility = Visibility.Hidden;
                    this.changePadLeft.Visibility = Visibility.Hidden;
                }
            }

        }

        private void detectUserAction()
        {
            if (this.skeleton != null)
            {
                SkeletonPoint leftHand = this.skeleton.Joints[JointType.HandLeft].Position;
                SkeletonPoint rightHand = this.skeleton.Joints[JointType.HandRight].Position;
                SkeletonPoint shoulderRight = this.skeleton.Joints[JointType.ShoulderRight].Position;
                SkeletonPoint shoulderLeft = this.skeleton.Joints[JointType.ShoulderLeft].Position;
                SkeletonPoint elbow = this.skeleton.Joints[JointType.ElbowLeft].Position;

                double deltaZ = 1.35*Math.Sqrt(Math.Pow(elbow.X - leftHand.X, 2) + Math.Pow(elbow.Y - leftHand.Y, 2) + Math.Pow(elbow.Z - leftHand.Z, 2));
                bool leftClick = Math.Sqrt(0.3 * Math.Pow(leftHand.X - shoulderLeft.X, 2) + 0.3 * Math.Pow(rightHand.Y - shoulderLeft.Y, 2) + Math.Pow(leftHand.Z - shoulderLeft.Z, 2)) > deltaZ;
                bool rightClick = Math.Sqrt(0.3 * Math.Pow(rightHand.X - shoulderRight.X, 2) + 0.3 * Math.Pow(rightHand.Y - shoulderRight.Y, 2) + Math.Pow(rightHand.Z - shoulderRight.Z, 2)) > deltaZ;

                this.HideAllKeys();

                if (rightClick || leftClick)
                {
                    PlaylistModel plModel = (PlaylistModel)this.model;
                    PadModel pm = (PadModel)plModel.Get(0);
                    bool isInThePad = this.isInThePad(leftHand, rightHand);

                    if (isInThePad)
                    {
                        this.ComputeKeysPosition(pm);
                    }

                    if (rightClick && isInThePad)
                    {
                        int rightKeyIndex = this.OnWhichKeyIsTheHand(rightHand);

                        if (this.rightIndex != rightKeyIndex)
                        {
                            this.rightIndex = rightKeyIndex;
                            this.timeRight = DateTime.Now;
                            this.startPlayingRight = true;
                        }

                        double dt = (DateTime.Now - this.timeRight).TotalMilliseconds;

                        //click on key with right hand
                        if (dt > this.keyDt && this.rightIndex > 2 && this.rightIndex < 15)
                        {
                            this.playingDt = 60000 / SongPlayer.myself.Bpm;
                            if (this.startPlayingRight || (DateTime.Now - this.timePlayingRight).TotalMilliseconds > this.playingDt)
                            {
                                this.startPlayingRight = false;
                                this.timePlayingRight = DateTime.Now;
                                this.Canvas.Children[this.rightIndex].Visibility = Visibility.Visible;
                                SongPlayer.myself.Play(plModel.Get(0).Get(this.rightIndex - 3).Stream);
                            }
                        }
                        // changing pad with right hand
                        else if (dt > this.padDt && this.rightIndex > 14)
                        {
                            if (this.startPlayingRight || (DateTime.Now - this.timePlayingRight).TotalMilliseconds > this.switchPadDt)
                            {
                                this.startPlayingRight = false;
                                this.timePlayingRight = DateTime.Now;
                                if (this.rightIndex == 15)
                                {
                                    plModel.ShiftFrom(1);
                                }
                                else if (this.rightIndex == 16)
                                {
                                    plModel.ShiftFrom(plModel.Count - 1);
                                }
                            }
                        }
                    }
                    else
                    {
                        this.startPlayingRight = true;
                    }

                    if (leftClick && isInThePad)
                    {
                        int leftKeyIndex = this.OnWhichKeyIsTheHand(leftHand);

                        if (this.leftIndex != leftKeyIndex)
                        {
                            this.leftIndex = leftKeyIndex;
                            this.timeLeft = DateTime.Now;
                            this.startPlayingLeft = true;
                        }
                        double dt = (DateTime.Now - this.timeLeft).TotalMilliseconds;

                        // click on key with left hand
                        if (dt > this.keyDt && this.leftIndex > 2 && this.leftIndex < 15)
                        {
                            this.playingDt = 60000 / SongPlayer.myself.Bpm;
                            if (this.startPlayingLeft || (DateTime.Now - this.timePlayingLeft).TotalMilliseconds > this.playingDt)
                            {
                                this.timePlayingLeft = DateTime.Now;
                                this.startPlayingLeft = false;
                                this.Canvas.Children[this.leftIndex].Visibility = Visibility.Visible;
                                SongPlayer.myself.Play(plModel.Get(0).Get(this.leftIndex - 3).Stream);
                            }
                        }
                        //changing pad with left hand
                        else if (dt > this.padDt && this.leftIndex > 14)
                        {
                            if (this.startPlayingLeft || (DateTime.Now - this.timePlayingLeft).TotalMilliseconds > this.playingDt)
                            {
                                this.timePlayingLeft = DateTime.Now;
                                this.startPlayingLeft = false;
                                if (this.leftIndex == 15)
                                {
                                    plModel.ShiftFrom(1);
                                }
                                else if (this.leftIndex == 16)
                                {
                                    plModel.ShiftFrom(plModel.Count - 1);
                                }
                            }
                        }
                    }
                    else
                    {
                        this.startPlayingLeft = true;
                    }
                }
                else
                {
                    this.startPlayingLeft = true;
                    this.startPlayingRight = true;
                }
            }
        }

        private bool isInThePad(SkeletonPoint LeftHand, SkeletonPoint RightHand){
            return true;
        }

        private void HideAllKeys(){
            for (int i = 3; i < this.Canvas.Children.Count-2; i++)
            {
                this.Canvas.Children[i].Visibility = Visibility.Hidden;
            }
        }

        private int OnWhichKeyIsTheHand(SkeletonPoint hand)
        {
            PlaylistModel plModel = (PlaylistModel)this.model;
            PadModel pm = (PadModel)plModel.Get(0);

            CoordinateMapper cm = new CoordinateMapper(this.ksensor);
            ColorImagePoint Hand = cm.MapSkeletonPointToColorPoint(hand, ColorImageFormat.RgbResolution640x480Fps30);
            double hX = Hand.X;
            double hY = Hand.Y;

            for (int i = 3; i < this.Canvas.Children.Count; i++)
            {
                double left = Canvas.GetLeft(this.Canvas.Children[i]);
                double right = Canvas.GetLeft(this.Canvas.Children[i]) + ((System.Windows.Shapes.Shape)this.Canvas.Children[i]).Width;
                double top = Canvas.GetTop(this.Canvas.Children[i]);
                double bottom = Canvas.GetTop(this.Canvas.Children[i]) + ((System.Windows.Shapes.Shape)this.Canvas.Children[i]).Height;

                if (hX > left && hX < right && hY > top && hY < bottom)
                {
                    return i;
                }
            }
            return -1;
        }

        private void ComputeKeysPosition(PadModel pm)
        {
            for (int i = 0; i < pm.Rows; i++)
            {
                for (int j = 0; j < pm.Columns; j++)
                {
                    System.Windows.Shapes.Rectangle rect = (System.Windows.Shapes.Rectangle)(this.Canvas.Children[3 + j + i * pm.Columns]);
                    rect.Width = this.scale * 3 / pm.Columns;
                    rect.Height = this.scale * 2 / pm.Rows;
                    SetKeyPosition(rect, skeleton.Joints[JointType.Head], pm.Rows, pm.Columns, i, j);
                }
            }
            for (int i = pm.Rows * pm.Columns + 3; i < this.Canvas.Children.Count - 2; i++)
            {
                System.Windows.Shapes.Rectangle rect = (System.Windows.Shapes.Rectangle)(this.Canvas.Children[i]);
                rect.Width = 0;
                rect.Height = 0;
                Canvas.SetTop(rect, 0);
                Canvas.SetLeft(rect, 0);
            }
        }

        private void RefreshScale(Joint shoulderRight, Joint shoulderLeft)
        {
            CoordinateMapper cm = new CoordinateMapper(this.ksensor);
            ColorImagePoint RightPoint = cm.MapSkeletonPointToColorPoint(shoulderRight.Position, ColorImageFormat.RgbResolution640x480Fps30);
            ColorImagePoint LeftPoint = cm.MapSkeletonPointToColorPoint(shoulderLeft.Position, ColorImageFormat.RgbResolution640x480Fps30);

            SkeletonPoint tricks = new SkeletonPoint();
            tricks.X = shoulderRight.Position.Z;
            tricks.Y = shoulderLeft.Position.Z;
            tricks.Z = (shoulderLeft.Position.Z +shoulderRight.Position.Z)/2;
            ColorImagePoint trickyPoint = cm.MapSkeletonPointToColorPoint(tricks, ColorImageFormat.RgbResolution640x480Fps30);
            
            double scale = Math.Sqrt(Math.Pow(LeftPoint.X - RightPoint.X, 2) + Math.Pow(LeftPoint.Y - RightPoint.Y, 2) + Math.Pow(trickyPoint.X - trickyPoint.Y , 2))/(5*tricks.Z);
            this.scale = scale;
        }

        private void SetPadPosition(System.Windows.Shapes.Rectangle rect, Joint joint)
        {
            CoordinateMapper cm = new CoordinateMapper(this.ksensor);
            ColorImagePoint point = cm.MapSkeletonPointToColorPoint(joint.Position, ColorImageFormat.RgbResolution640x480Fps30);

            rect.Width = 3 * this.scale;
            rect.Height = 2 * this.scale;
            Canvas.SetLeft(rect, point.X - 1.5 * this.scale);
            Canvas.SetTop(rect, point.Y - 50);
            /*this.padTop = point.Y;
            this.padLeft = point.X - 1.5 * this.scale;
            this.padRight = this.padLeft + rect.Width;
            this.padBottom = this.padTop + rect.Height;*/
        }

        public void SetKeyPosition(System.Windows.Shapes.Rectangle rect, Joint joint, int nbRows, int nbCols, int i, int j)
        {
            CoordinateMapper cm = new CoordinateMapper(this.ksensor);
            ColorImagePoint point = cm.MapSkeletonPointToColorPoint(joint.Position, ColorImageFormat.RgbResolution640x480Fps30);

            Canvas.SetTop(rect, point.Y + i * rect.Height - 50);
            Canvas.SetLeft(rect, (point.X - 1.5 * this.scale) + j * rect.Width);
        }

        public void SetSwitchPadRightPosition()
        {
            this.changePadRight.Height = (2*this.scale)/3;
            this.changePadRight.Width = this.scale / 2;
            Canvas.SetTop(this.changePadRight, Canvas.GetTop(this.MainPad) + (this.MainPad.Height / 2) - (this.changePadRight.Height / 2));
            Canvas.SetLeft(this.changePadRight, Canvas.GetLeft(this.MainPad) + this.MainPad.Width);

            PlaylistModel plModel = (PlaylistModel)this.model;
            PadModel pm = (PadModel)plModel.Get(1);
            this.changePadRight.Fill = new SolidColorBrush(pm.Color);
        }
        
        public void SetSwitchPadLeftPosition()
        {
            this.changePadLeft.Height = (2 * this.scale) / 3;
            this.changePadLeft.Width = this.scale / 2;
            Canvas.SetTop(this.changePadLeft, Canvas.GetTop(this.MainPad) + (this.MainPad.Height / 2) - (this.changePadRight.Height / 2));
            Canvas.SetLeft(this.changePadLeft, Canvas.GetLeft(this.MainPad) - this.changePadLeft.Width);
        
            PlaylistModel plModel = (PlaylistModel)this.model;
            PadModel pm = (PadModel)plModel.Get(plModel.Count-1);
            this.changePadLeft.Fill = new SolidColorBrush(pm.Color);
        }

        private void SetEllipsePosition(Ellipse ellipse, Joint joint)
        {
            CoordinateMapper cm = new CoordinateMapper(this.ksensor);
            ColorImagePoint point = cm.MapSkeletonPointToColorPoint(joint.Position, ColorImageFormat.RgbResolution640x480Fps30);

            Canvas.SetLeft(ellipse, point.X);
            Canvas.SetTop(ellipse, point.Y);
        }
    }
}
